/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.parser;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import dex.compiler.model.base.Place;
import dex.compiler.model.expression.Call;
import dex.compiler.model.expression.DirectCall;
import dex.compiler.model.expression.Expression;
import dex.compiler.model.expression.IdentifierExpression;
import dex.compiler.model.expression.Infix;
import dex.compiler.model.statement.Assignment;
import dex.compiler.model.statement.Block;
import dex.compiler.model.statement.CallStatement;
import dex.compiler.model.statement.CatchBlock;
import dex.compiler.model.statement.Comment;
import dex.compiler.model.statement.Declaration;
import dex.compiler.model.statement.DecrementStatement;
import dex.compiler.model.statement.ForStatement;
import dex.compiler.model.statement.IfStatement;
import dex.compiler.model.statement.IncrementStatement;
import dex.compiler.model.statement.ReturnStatement;
import dex.compiler.model.statement.Statement;
import dex.compiler.model.statement.SwitchCase;
import dex.compiler.model.statement.SwitchStatement;
import dex.compiler.model.statement.ThrowStatement;
import dex.compiler.model.statement.TryStatement;
import dex.compiler.model.statement.WhileStatement;
import dex.compiler.model.type.BasicTypeNode;
import dex.compiler.model.type.TypeNode;


/**
 * A parser for statement objects.
 */
class StatementParser extends ExpressionParser {

	
	/**
	 * Character sequences that indicate a type tail.
	 */
	final private static List<String> TYPE_TAILS = Collections.unmodifiableList(Arrays.asList(
	 new String[] { "[]", "<(" }
	));


	/**
	 * The keywords that begin blocks in a switch statement.
	 */
	final private static List<String> SWITCHES = Collections.unmodifiableList(Arrays.asList( 
	 new String[] { "case", "default" }	
	));


	
	/**
	 * Constructs a new StatementParser.
	 * 
	 * @param config  the configuration for the new parser
	 */
	public StatementParser(ParserConfig config) {
		super(config);
	}
	
	
	/**
	 * Parses a declaration.  Expects a type followed by an identifier
	 * and optional initializer.
	 * 
	 * @return  the parsed declaration
	 */
	public Statement parseDeclaration() {
		TypeNode t = parseType();
		return parseDeclaration(t);
	}
	
	
	/**
	 * Parses a declaration whose type was already parsed.
	 * 
	 * @param t  the previously parsed type
	 * @return   the newly parsed declaration statement
	 */
	public Statement parseDeclaration(TypeNode t) {
		String id = parseIdentifier();
		Expression initializer = null;
		char ch = input.skipAndPeek();
		if (ch == '=') {
			input.expect('=');
			initializer = parseExpression();
		}
		return new Declaration(input.getPlace(), t, id, initializer);
	}
	
	
	/**
	 * Parses a statement.
	 * 
	 * @return  the parsed statement
	 */
	public Statement parseStatement() {
		
		// New local scope?
		if (input.skipAndPeek() == '{') {
			return parseBlock();
		}
		
		if (input.skipAndPeek() == '/') {
			return parseComment();
		}
		
		// Check all the control flow reserved words...
		
		if (testKeyword("while")) {
			return parseWhile();
		}
		
		if (testKeyword("for")) {
			return parseFor();
		}
		
		if (testKeyword("if")) {
			return parseIf();
		}
		
		if (testKeyword("switch")) {
			return parseSwitch();
		}
		
		if (testKeyword("try")) {
			return parseTry();
		}
		
		if (testKeyword("return")) {
			return parseReturn();
		}
		
		if (testKeyword("void")) {
			return parseVoid();
		}
		
		if (testKeyword("throw")) {
			return parseThrow();
		}
		
		// Okay, at this point we know it's not a reserved word.
		
		// The next thing in the stream might be a type.
		// We can quickly tell whether it's a local user type.
		char ch = input.skipAndPeek();
		if (ch == '#') {
			return parseDeclaration(); 
		}
		
		// Okay, not an obvious type, anyway.
		// But the next thing in the stream MUST be an identifier.
		// The identifier can be a type (signifying a declaration),
		// or a function call, or a variable.
		String id = parseIdentifier();

		// If [] follows the id, that indicates a dynamic array type.
		// If <( follows the id, that indicates a function type.
		// If a type is indicated, then this statement must be a declaration.
		if (input.test(TYPE_TAILS, AnyBreaks.INSTANCE) != null) {
			TypeNode t = new BasicTypeNode(input.getPlace(), false, getName(id));
			return parseDeclaration(parseTypeTail(t));
		}
		
		// If ( follows the id, that indicates a function call.
		if (input.skipAndPeek() == '(') {
			DirectCall dc = new DirectCall(input.getPlace(), id, parseExpressionList());
			return new CallStatement(input.getPlace(), dc);
		}
		
		ch = input.skipAndPeek();
		
		// An identifier followed by another identifier is a declaration
		// (identifier 1 is the type, followed by the local variable name)
		if (Character.isJavaIdentifierStart(ch)) {
			BasicTypeNode t = new BasicTypeNode(input.getPlace(), false, getName(id));
			return parseDeclaration(t);
		}
		
		Expression lvalue;
		
		// Not a block.
		// Not a control statement.
		// Not a declaration.
		// It must be an assignment.
		lvalue = new IdentifierExpression(input.getPlace(), id);
		lvalue = parseOperandTail(lvalue);
		
		// ...except I lied, the above parseOperandTail may have returned 
		// an expression chain that ends in a Call, eg:
		//
		//    f.x.y[2].foo()
		//
		// Since you can't assign to the result of a function call,
		// we want to convert that call expression to a statement
		// and return that.
		if (lvalue instanceof Call) {
			Call call = (Call)lvalue;
			return new CallStatement(input.getPlace(), call);
		}
		
		// Aren't you glad I did this by hand instead of using bison?
		
		// It must be an assignment.  We've got the lvalue; now we
		// need the assignment operator.
 		
		// Test for special case of ++
		if (input.test("++", SymbolBreaks.INSTANCE)) {
			input.expect("++", SymbolBreaks.INSTANCE);
			return new IncrementStatement(input.getPlace(), lvalue);
		}

		// Also --
		if (input.test("--", SymbolBreaks.INSTANCE)) {
			input.expect("--", SymbolBreaks.INSTANCE);
			return new DecrementStatement(input.getPlace(), lvalue);
		}
		
		Infix assignmentOp;
  		
		ch = input.skipAndPeek();
		if (ch == '=') {
			assignmentOp = null; // null value represents straight assignment
		} else {
			String symbol = pickOperator();
			if (symbol == null) {
				raise("Expected assignment operator.");
			}
			assignmentOp = Infix.get(symbol);
		}
		

		// Otherwise, whatever operator we just parsed must be follwed
		// by an = sign since it's an assignment.  
		// Eg, if we parsed + then we expect +=
		input.expect('=');
		
		Expression rvalue = parseExpression();
		
		return new Assignment(input.getPlace(), assignmentOp, lvalue, rvalue);
	}
	
	
	/**
	 * Parses a return statement.
	 * 
	 * @return  the parsed return statement
	 */
	public ReturnStatement parseReturn() {
		input.skipAndExpect("return", WordBreaks.INSTANCE);

		return new ReturnStatement(input.getPlace(), parseExpression());
	}
	
	
	/**
	 * Parses a return statement that does not return a value.
	 * 
	 * @return  a return statement that does not return a value
	 */
	public ReturnStatement parseVoid() {
		Place place = input.getPlace();
		input.skipAndExpect("void", WordBreaks.INSTANCE);
		return new ReturnStatement(place, null);
	}
	
	
	/**
	 * Parses a throw statement
	 * 
	 * @return  the parsed throw statement
	 */
	public ThrowStatement parseThrow() {
		input.skipAndExpect("throw", WordBreaks.INSTANCE);
		return new ThrowStatement(input.getPlace(), parseExpression());
	}


	/**
	 * Parses a block of statements.
	 * 
	 * @return  the parsed block
	 */
	public Block parseBlock() {
		input.skipAndExpect('{');
		List<Statement> statements = new ArrayList<Statement>();
		char ch = input.skipAndPeek();
		while ((ch != 0) && (ch != '}')) {
			statements.add(parseStatement());
			ch = input.skipAndPeek();
		}
		input.expect('}');
		return new Block(input.getPlace(), statements);
	}
	
	
	/**
	 * Parses a while statement.
	 * 
	 * @return  the parsed while statement
	 */
	public WhileStatement parseWhile() {
		input.skipAndExpect("while", WordBreaks.INSTANCE);
		Expression test = parseExpression();
		Statement body = parseStatement();
		return new WhileStatement(input.getPlace(), test, body);
	}
	
	
	/**
	 * Parses a for statement.
	 * 
	 * @return  the parsed for statement
	 */
	public ForStatement parseFor() {
		input.skipAndExpect("for", WordBreaks.INSTANCE);
		Statement initializer = parseStatement();
		input.skipAndExpect(';');
		Expression test = parseExpression();
		input.skipAndExpect(';');
		Statement modifier = parseStatement();
		Statement body = parseStatement();
		return new ForStatement(input.getPlace(), initializer, test, modifier, body);
	}
	
	
	/**
	 * Parses an if statement.
	 * 
	 * @return  the parsed if statement
	 */
	public IfStatement parseIf() {
		input.skipAndExpect("if", WordBreaks.INSTANCE);
		Expression test = parseExpression();
		Statement positive = parseStatement();
		Statement negative;
		if (testKeyword("else")) {
			input.expect("else", WordBreaks.INSTANCE);
			negative = parseStatement();
		} else {
			negative = null;
		}
		return new IfStatement(input.getPlace(), test, positive, negative);
	}
	
	
	/**
	 * Parses a switch statement.
	 * 
	 * @return the parsed switch statement
	 */
	public SwitchStatement parseSwitch() {
		input.skipAndExpect("switch", WordBreaks.INSTANCE);
		Expression test = parseExpression();
		List<SwitchCase> cases = new ArrayList<SwitchCase>();
		Statement defaultStatement = null;
		input.skipAndExpect('{');
		while (input.skipAndTest(SWITCHES, WordBreaks.INSTANCE) != null) {
			if (testKeyword("case")) {
				cases.add(parseCase());
			} else {
				// must be default keyword
				input.expect("default", WordBreaks.INSTANCE);
				if (defaultStatement != null) {
					raise("More than one default for switch.");
				}
				defaultStatement = parseStatement();
			}
		}
		input.skipAndExpect('}');
		return new SwitchStatement(input.getPlace(), test, cases, defaultStatement);
	}

	
	/**
	 * Parses a case of a switch statement.
	 * 
	 * @return  the parsed case
	 */
	public SwitchCase parseCase() {
		input.skipAndExpect("case", WordBreaks.INSTANCE);
		List<Expression> tests = new ArrayList<Expression>();
		tests.add(parseExpression());
		char ch = input.skipAndPeek();
		while (ch == ',') {
			input.expect(',');
			tests.add(parseExpression());
			ch = input.skipAndPeek();
		}
		Statement body = parseStatement();
		return new SwitchCase(input.getPlace(), tests, body);
	}
	
	
	/**
	 * Parses a try/catch/finally statement.
	 * 
	 * @return the parsed try/catch/finally statement
	 */
	public TryStatement parseTry() {
		input.skipAndExpect("try", WordBreaks.INSTANCE);
		Block trySection = parseBlock();
		List<CatchBlock> catchSection = new ArrayList<CatchBlock>();
		Block finallySection = null;
		while (testKeyword("catch")) {
			input.expect("catch", WordBreaks.INSTANCE);
			TypeNode type = parseType();
			String id = parseIdentifier();
			Block block = parseBlock();
			catchSection.add(new CatchBlock(input.getPlace(), type, id, block));
		}
		if (testKeyword("finally")) {
			input.expect("finally", WordBreaks.INSTANCE);
			finallySection = parseBlock();
		}
		return new TryStatement(input.getPlace(), trySection, catchSection, finallySection);
	}
	
	
	/**
	 * Parses a comment.
	 * 
	 * @return  the parsed comment
	 */
	public Comment parseComment() {
		char ch = input.skipAndPeek();
		if (ch != '/') {
			raise("Expected comment.");
		}
		input.expect('/');
		ch = input.peek();
		String message;
		if (ch == '*') {
			input.expect('*');
			message = input.readUntil("*/"); 
		} else if (ch == '/') {
			input.expect('/');
			message = input.readUntil("\n");
		} else {
			raise("Expected comment.");
			throw new AssertionError();
		}
		return new Comment(input.getPlace(), message);
	}

}
